package com.novartis.nims.trialsubmitter;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.novartis.nims.util.RecursiveFileListIterator;
import com.pixelmed.dicom.Attribute;
import com.pixelmed.dicom.AttributeList;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.TagFromName;

public class ImageDirectoryDOMParser {
	
	private Document mDocument;
	private String mTrialDirectory;
	
	private HashMap<String, Element> mVisitMap;
	private HashMap<String, Element> mScanMap;
	private HashMap<String, Integer> mScanRepeatMap;
	
	private PropertyChangeSupport mPropertyChangeSupport;
	
	// bound properties
	private int mProgressInBytes = 0;
	
	ImageDirectoryDOMParser(PropertyChangeListener listener, Document document, String trialDirectory, boolean visitsOrganized) {
		mDocument = document;
		mTrialDirectory = trialDirectory;
		mVisitMap = new HashMap<String, Element>();
		mScanMap = new HashMap<String, Element>();
		mScanRepeatMap = new HashMap<String, Integer>();
		
		mPropertyChangeSupport = new PropertyChangeSupport(this);
		mPropertyChangeSupport.addPropertyChangeListener(listener);
		
		int totalBytes = calculateDirectorySize();
		//System.out.println(totalBytes);
		mPropertyChangeSupport.firePropertyChange("totalBytes", 0, totalBytes);
		
		parseTrial(visitsOrganized);
	}
	
	private int calculateDirectorySize() {
		double totalBytes = 0;
		
		RecursiveFileListIterator fileIterator = new RecursiveFileListIterator(new File(mTrialDirectory));
		while (fileIterator.hasNext()) {
			File file = fileIterator.next();
			totalBytes += file.length();
		}
		return (int)totalBytes; //round to integer
	}
	
	private void updateProgressInBytes(int newValue) {
		int oldValue = mProgressInBytes;
		mProgressInBytes += newValue;
		mPropertyChangeSupport.firePropertyChange("progressInBytes", oldValue, mProgressInBytes);
	}

	private void parseTrial(boolean visitsOrganized) {
		Element root = mDocument.getDocumentElement();
		File rootDirectory = new File(mTrialDirectory);
		Element study = mDocument.createElement("Study");
		// study ID is input directory name
		study.setAttribute("id", rootDirectory.getName());
		root.appendChild(study);
		
		// register each child directory as a new subject
		File[] subjects = rootDirectory.listFiles();
		for (int subjectIndex = 0; subjectIndex < subjects.length; subjectIndex++) {
			File subjectFile = subjects[subjectIndex];
			String subjectString = subjectFile.getName();
			
			Element subjectNode = mDocument.createElement("Subject");
			subjectNode.setAttribute("id", subjectString);
			study.appendChild(subjectNode);
			
			if (!visitsOrganized) {
				//traverse(subjectFile, subjectNode);
				RecursiveFileListIterator fileIterator = new RecursiveFileListIterator(subjectFile);
				while (fileIterator.hasNext()) {
					File file = fileIterator.next();
					parseFile(file, subjectNode);
				}
			} else {
				// offset into into absolute path to mark beginning of relative path section
				int pathOffset = rootDirectory.getParentFile().getAbsolutePath().length() + 1;
			
				File[] visits = subjectFile.listFiles();
				for (int visitIndex = 0; visitIndex < visits.length; visitIndex++) {
					File visitFile = visits[visitIndex];
					String visitString = visitFile.getName();

					Element visitNode = mDocument.createElement("Visit");
					visitNode.setAttribute("id", visitString);
					subjectNode.appendChild(visitNode);

					File[] scans = visitFile.listFiles();
					for (int scanIndex = 0; scanIndex < scans.length; scanIndex++) {
						File scanFile = scans[scanIndex];
						String scanString = scanFile.getName();

						Element scanNode = mDocument.createElement("Scan");
						scanNode.setAttribute("id", scanString);
						visitNode.appendChild(scanNode);

						File[] imageFiles = scans[scanIndex].listFiles();
						for (int filesIndex = 0; filesIndex < imageFiles.length; filesIndex++) {
							String relativePath =
								imageFiles[filesIndex].getAbsolutePath().substring(pathOffset);
							//TODO: replace \ with /

							Element imageFileNode = mDocument.createElement("ImageFile");
							imageFileNode.setAttribute("name", relativePath);
							// TODO anonymize and hash right now?
							imageFileNode.setAttribute("sha1hash", "");
							scanNode.appendChild(imageFileNode);
						} // for each file
					} // for each scan
				} // for each visit
			} // if (!visitsOrganized) else
		}
	}
	
	private void parseFile(File file, Node parentNode) {
		DicomInputStream dis = null;
		AttributeList list = new AttributeList();
		try {
			dis = new DicomInputStream(file);
			list.read(dis);
		} catch (DicomException de) {
			rejectFile("WARNING: " + file.getName() + " rejected. Not a valid DICOM.");
			return;
		} catch (IOException ioe) {
			rejectFile("WARNING: " + file.getName() + " rejected. Unreadable or unparseable.");
			return;
		}

		
		// DICOM attribute values used to populate required metadata in the DOM model
		// reject file if any are missing or empty.
		
		Attribute attr = list.get(TagFromName.AcquisitionDate);
		if (attr == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Series Instance UID missing.");
			return;
		}
		String visitKey =
			attr.getSingleStringValueOrNull();
		
		if (visitKey == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Acquisition Date empty or missing.");
			return;
		}
		
		
		attr = list.get(TagFromName.SeriesInstanceUID);
		if (attr == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Series Instance UID missing.");
			return;
		}		
		String scanKey =
			attr.getSingleStringValueOrNull();

		if (scanKey == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Series Instance UID empty.");
			return;
		}

		
		attr = list.get(TagFromName.Modality);
		if (attr == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Modality missing.");
			return;
		}	
		String scanName =
			attr.getSingleStringValueOrNull();

		if (scanName == null) {
			rejectFile("WARNING: " + file.getName() + " rejected. DICOM attribute Modality empty or missing.");
			return;
		}
		
		// hardcoded metadata for DOM
		String visitName = visitKey;
		String visitRepeat = null;

		// filesystem coded metadata for DOM
		int pathOffset = new File(mTrialDirectory).getParentFile().getAbsolutePath().length() + 1;
		String fileKey = file.getAbsolutePath().substring(pathOffset);
		//TODO: replace \ with /
		
				
		Element visitNode = mVisitMap.get(visitKey);
		if (visitNode == null) {
			visitNode = mDocument.createElement("Visit");
			visitNode.setAttribute("id", visitName);
			if (visitRepeat != null) {
				visitNode.setAttribute("repeatkey", visitRepeat);
			}
			parentNode.appendChild(visitNode);
			mVisitMap.put(visitKey, visitNode);
		}
		
		Element scanNode = mScanMap.get(scanKey);
		if (scanNode == null) {
			scanNode = mDocument.createElement("Scan");
			scanNode.setAttribute("id", scanName);
			
			Integer scanRepeat = mScanRepeatMap.get(scanName);
			String scanRepeatString = null;
			int newRepeatVal = 1;
			if (scanRepeat == null) {
				scanRepeatString = "1";
			} else {
				newRepeatVal = scanRepeat.intValue() + 1;
				scanRepeatString = String.valueOf(newRepeatVal);
			}
			mScanRepeatMap.put(scanName, newRepeatVal);
			scanNode.setAttribute("repeatkey", scanRepeatString);
			visitNode.appendChild(scanNode);
			mScanMap.put(scanKey, scanNode);
		}
		
		// method is private, so use is tightly controlled: only called on unique files per instance
		// if access changes, update to throw an exception on repeated file key 
		Element fileNode = mDocument.createElement("ImageFile");
		fileNode.setAttribute("name", fileKey);
		fileNode.setAttribute("sha1hash", "");
		scanNode.appendChild(fileNode);
		
		updateProgressInBytes((int)file.length());
	}
	
	private void rejectFile(String message) {
		// file ignored
		// TODO: log it and continue
		System.out.println(message);
	}
}
